package com.pi.shisan.service.config;

import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class ListenerHttpClient extends CloseableHttpClient {

    private CloseableHttpClient closeableHttpClient;

    private Map<CloseableHttpClient, AtomicInteger> counterMapping = new ConcurrentHashMap<>(2);

    /**
     * 遇到哪些错误时需要重新创建新的HttpClient
     */
    private Set<Integer> errCodes = new HashSet<>(Arrays.asList(404));

    /**
     * 错误累计几次时重新创建新的HttpClient
     */
    private static final int errTimes = 5;

    public ListenerHttpClient(){
        this.closeableHttpClient = initNew();
    }

    private CloseableHttpClient initNew(){
        if(closeableHttpClient != null){
            try {
                closeableHttpClient.close();
            } catch (Exception e) {
            }
        }
        counterMapping.clear();
        PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager();
        httpClientConnectionManager.setMaxTotal(50);
        httpClientConnectionManager.setDefaultMaxPerRoute(10);

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(300000)
                .setSocketTimeout(300000)
                .setConnectionRequestTimeout(300000)
                .build();
        // 设置重定向策略
        LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();

        //必须配置，否则尤其是在测试环境大量图片404情况下容易卡死
        SocketConfig socketConfig = SocketConfig.custom()
                .setSoKeepAlive(false)
                .setSoLinger(1)
                .setSoReuseAddress(true)
                .setSoTimeout(10000).build();

        CloseableHttpClient client = HttpClientBuilder.create()
                .setConnectionManager(httpClientConnectionManager)
                .setDefaultRequestConfig(requestConfig)
                .setRedirectStrategy(redirectStrategy)
                .setDefaultSocketConfig(socketConfig)
                .setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36")
                .build();
        counterMapping.put(client, new AtomicInteger(0));
        return client;
    }

    @Override
    protected CloseableHttpResponse doExecute(HttpHost httpHost, HttpRequest httpRequest, HttpContext httpContext) throws IOException, ClientProtocolException {
        throw new RuntimeException("无需支持！");
    }

    @Override
    public void close() throws IOException {
        closeableHttpClient.close();
    }

    @Override
    public HttpParams getParams() {
        return closeableHttpClient.getParams();
    }

    @Override
    public ClientConnectionManager getConnectionManager() {
        return closeableHttpClient.getConnectionManager();
    }

    @Override
    public CloseableHttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) throws IOException, ClientProtocolException {
        executeBeforeCheck(closeableHttpClient);
        return checkHttpResponse(closeableHttpClient,closeableHttpClient.execute(target,request,context));
    }

    @Override
    public CloseableHttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException, ClientProtocolException {
        executeBeforeCheck(closeableHttpClient);
        return checkHttpResponse(closeableHttpClient,closeableHttpClient.execute(request, context));
    }

    @Override
    public CloseableHttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException {
        executeBeforeCheck(closeableHttpClient);
        return checkHttpResponse(closeableHttpClient,closeableHttpClient.execute(request));
    }

    @Override
    public CloseableHttpResponse execute(HttpHost target, HttpRequest request) throws IOException, ClientProtocolException {
        executeBeforeCheck(closeableHttpClient);
        return checkHttpResponse(closeableHttpClient,closeableHttpClient.execute(target, request));
    }

    @Override
    public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException, ClientProtocolException {
        executeBeforeCheck(closeableHttpClient);
        return closeableHttpClient.execute(request, r->{
            checkHttpResponse(closeableHttpClient, (CloseableHttpResponse) r);
            return responseHandler.handleResponse(r);
        });
    }

    @Override
    public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context) throws IOException, ClientProtocolException {
        executeBeforeCheck(closeableHttpClient);
        return closeableHttpClient.execute(request,
                r->{
                    checkHttpResponse(closeableHttpClient, (CloseableHttpResponse) r);
                    return responseHandler.handleResponse(r);
                }
                , context);
    }

    @Override
    public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler) throws IOException, ClientProtocolException {
        executeBeforeCheck(closeableHttpClient);
        return closeableHttpClient.execute(target, request,
                r->{
                    checkHttpResponse(closeableHttpClient, (CloseableHttpResponse) r);
                    return responseHandler.handleResponse(r);
                }
            );
    }

    @Override
    public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context) throws IOException, ClientProtocolException {
        executeBeforeCheck(closeableHttpClient);
        return closeableHttpClient.execute(target, request,
                r->{
                    checkHttpResponse(closeableHttpClient, (CloseableHttpResponse) r);
                    return responseHandler.handleResponse(r);
                }
                , context);
    }

    private CloseableHttpResponse checkHttpResponse(CloseableHttpClient client,CloseableHttpResponse response){
        if(response != null){
            int errCode = response.getStatusLine().getStatusCode();
            if(errCode != 200){
                if(errCodes.contains(errCode)){
                    AtomicInteger errCounter = counterMapping.get(client);
                    if(errCounter != null){
                        errCounter.incrementAndGet();
                    }
                }
            }
        }
        return response;
    }

    private void executeBeforeCheck(CloseableHttpClient client){
        AtomicInteger errCounter = counterMapping.get(client);
        if(errCounter == null || errCounter.get() >= errTimes){
            synchronized (this){
                errCounter = counterMapping.get(client);
                if(errCounter == null || errCounter.get() >= errTimes){
                    this.closeableHttpClient = initNew();
                }
            }
        }
    }
}
